#include "6502.h"
#include "romLoader.h"
#include "NESMemory.h"
#include "NES.h"
#include "SDL/SDL.h"
#include "JRYNES.h"
#include "6502_Debug.h"
#include "Statistics.h"
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <signal.h>

const static char optString[] = "f:x:y:r:p:mwdlnqo::e:?\0";

EmulationSettings Settings;

#ifdef DEBUGCONSOLE
extern __Debug_Context Debug;
#endif

static const struct option longOpts[] = {
    { "filename", required_argument, NULL, 'f' },
    { "no_delay", no_argument, NULL, 'n' },
    { "scale_x", required_argument, NULL, 'x' },
    { "scale_y", required_argument, NULL, 'y' },
    { "more_sprites", no_argument, NULL, 'm' },
    { "record_movie", required_argument, NULL, 'r'},
    { "play_movie", required_argument, NULL, 'p'},
    { "quit_on_movie_end", no_argument, NULL, 'q'},
    { "debug_console", no_argument, NULL, 'd' },
    { "dbt", no_argument, NULL, 'b' },
    { "epoch_length", required_argument, NULL, 'e'},
    //{ "parallel_compilation", no_argument, NULL, 'l'},
    //{ "o", optional_argument, NULL, 'o'},
    //{ "dont_draw", no_argument, NULL, 'w' },
    //{ "elapsed_cycles_graph", optional_argument, NULL, 'e' },
    { NULL, no_argument, NULL, 0 }
};

void sigInt(int Code){
#ifdef DEBUGCONSOLE
    Debug.OperationFlags |= DBGOP_STEP;
#else
    printf("Terminating emulation...\n\n");
    TerminateEmulation();
    exit(1);
#endif
}

void showHelp(){
    
    printf("\n");
    
    printf("This is jrynes, an experimental NES emulator\n\n");
    
    printf("usage: jrynes [OPTIONS] --filename ROM_Filename\n\n");
    
    printf("OPTIONS are:\n\n");
    
    printf("Video Output:\n");
    printf("\t[--scale_x|-x] x-scaling-factor\n");
    printf("\t[--scale_y|-y] y-scaling-factor\n\n");
    
    printf("Display more than 8 sprites per scanline:\n");
    printf("\t[--more_sprites|-m]\n\n");
    
    printf("Show debug console at the start of the execution:\n");
    printf("\t[--debug_console|-d]\n\n");
    
    printf("No delay between frames:\n");
    printf("\t[--no_delay|-n]\n\n");
    
    printf("Don't draw frames\n");
    printf("\t[--dont_draw|-w]\n\n");    
    
    printf("Movie recording/playback options:\n");
    printf("\t[--record_movie|-r] movie-filename\n");
    printf("\t[--play_movie|-p] movie-filename\n");
    printf("\t[--quit_on_movie_end|-q]\n\n");
    
    /*
    printf("Cycle Graph Generation\n");
    printf("\t[--elapsed_cycles_graph|-e]  -> Enable elapsed cycles graph generation.\n");
    printf("\t\tThis generates a report that shows the total of elapsed cycles\n\n");
    
    printf("\t[-ea:ARGUMENTS]         -> Sets options related to how to generate the elapsed cycles report.\n");
    printf("\t\tImplies -e. Arguments should be separated by commas.\n\n");
    
    printf("\t\tAvailable arguments:\n\n");
    
    printf("\t\tperiod=time                  -> Indicates how often a cycle measurement should be made. This should be in microseconds.\n");
    printf("\t\t\t(Default = 100th of a second. Equivalent to 10000)\n");
    printf("\t\tfilename=filename            -> Sets the name of the file to write the csv report into. (Default = cyclegraph.csv)\n\n");
    */
    
    /*
    printf("Dynamic Binary Translation (DBT) options:\n");
    printf("\t[--dbt|-b]                     -> Enable DBT\n\n");
    */
    
    /*
    printf("\t[--parallel_compilation|-l]    -> Enable DBT Parallel Compilation Technique\n");
    printf("\t\tParallel compilation only makes sense on DBT enabled. It does all DBT Scanning and Translation tasks\n");
    printf("\t\tusing separate threads of execution in order to mask out the slow start phenomenon.\n");
    printf("\t\tWARNING - Movies recorded with this feature enabled cannot be played back correctly.\n\n");    
    */
    
    #ifdef ASMUTILITIES
        printf("Compiled with ASM Utilities\n\n");
    #else
        printf("Compiled without ASM Utilities\n\n");
    #endif
    
}

static int FindNextChar(char* String, int InitialPos, char Which){
    int i;
    
    for(i = InitialPos; ((String[i]) && (String[i] != Which)); i++);
    
    return i;
}

static void ParseElapedCyclesArguments(char* Arguments){
    char* Option;
    int PosI, PosF, i, j;
    char *Temp;
    
    Settings.ContextMask = 0;
    
    Temp = calloc(0x100, 1);
    
    if(Arguments){
        
        Option = strstr(Arguments, "period=");
        if(Option){
            j = 0;
            printf("%s\n", Option);
            PosI = FindNextChar(Option, 0, '=');
            PosF = FindNextChar(Option, PosI, ',');
            for(i = PosI + 1; i < PosF; i++, j++)
                Temp[j] = Option[i];
            Temp[j] = 0;
            Settings.CycleCounterPeriod = atoi(Temp);
        }     
        else
            Settings.CycleCounterPeriod = 10000;
        
        Option = strstr(Arguments, "filename=");
        if(Option){
            j = 0;
            printf("%s\n", Option);
            PosI = FindNextChar(Option, 0, '=');
            PosF = FindNextChar(Option, PosI, ',');
            for(i = PosI + 1; i < PosF; i++, j++)
                Temp[j] = Option[i];
            Temp[j] = 0;
            strcpy(Settings.CycleCounterFilename, Temp);
        }     
        else
            strcpy(Settings.CycleCounterFilename, "cyclegraph.csv");
        
    }
    else{
        Settings.CycleCounterPeriod = 10000;
        strcpy(Settings.CycleCounterFilename, "cyclegraph.csv");
    }
    
    free(Temp);    
}

int main(int argc, char *argv[]){
    char* RomFilename = NULL;
    int opt;
    int longIndex;
    int thresholdSet = 0, epochSizeSet = 0;
    int retVal;
    
    memset(&Settings, 0, sizeof(EmulationSettings));
    
    //setup signal handlers
    struct sigaction SigIntAction;
    SigIntAction.sa_handler = sigInt;
    sigemptyset(&(SigIntAction.sa_mask));
    sigaction(SIGINT, &SigIntAction, NULL);
    
    opt = getopt_long(argc, argv, optString, longOpts, &longIndex);
    
    while(opt != -1){
        switch(opt){
        
            //Movie Filename - Recording
            case 'r':
                Settings.RecordMovieFile = malloc(strlen(optarg) + 1);
                strcpy(Settings.RecordMovieFile, optarg);
                Settings.ArgumentFlags |= AF_RECORDING;
                printf("Recording movie to file %s\n", optarg);
            break;
            
            //Movie Filename - Playing
            case 'p':
                Settings.PlayMovieFile = malloc(strlen(optarg) + 1);
                strcpy(Settings.PlayMovieFile, optarg);
                Settings.ArgumentFlags |= AF_PLAYING;
                printf("Playing movie from file %s\n", optarg);
            break;
            
            //Rom Filename
            case 'f':
                RomFilename = malloc(strlen(optarg) + 1);
                Settings.RomFilename = RomFilename;
                strcpy(RomFilename, optarg);
            break;
            
            //X Scaling factor
            case 'x':
                Settings.xScaling = atoi(optarg);
            break;
            
            //Y Scaling factor
            case 'y':
                Settings.yScaling = atoi(optarg);
            break;
            
            //Show more than 8 sprites onscreen
            case 'm':
                Settings.ArgumentFlags |= AF_MORE_SPRITES;
            break;
            
            //Debug console on startup
            case 'd':
                Settings.ArgumentFlags |= AF_DEBUG_CONSOLE;
            break;
            
            //Use Dynamic Binary Translation
            case 'b':
                Settings.ArgumentFlags |= AF_DBT;
            break;
            
            //Set compilation threshold for block sizes
            case 't':
                Settings.CompilationThreshold = atoi(optarg);
                thresholdSet = 1;
            break;
            
            //Use DBT Parallel Compilation Technique
            case 'l':
                Settings.ArgumentFlags |= AF_DBT_PARALLEL_COMPILATION;
            break;
            
            //No delay. Useful for assessing speed.
            case 'n':
                Settings.ArgumentFlags |= AF_NO_DELAY;
            break;
            
            //Quit as soon as the movie ends. Also prints out CPU context.
            case 'q':
                Settings.ArgumentFlags |= AF_QUIT_MOVIE_END;
            break;            
                        
            //Handle optimizations
            case 'o':
                if(!strcmp(optarg, "immediate"))
                    Settings.ArgumentFlags |= AF_OPT_IMMEDIATE;
                if(!strcmp(optarg, "block_coalescing"))
                    Settings.ArgumentFlags |= AF_OPT_BLOCK_COALESCING;                
            break;                
            
            case 'e':
                Settings.EpochLength = atoi(optarg);
                epochSizeSet = 1;
            break;
            
            //Don't draw on screen
            case 'w':
                Settings.ArgumentFlags |= AF_DONT_DRAW;
            break;              
            
            //Help
            case '?':
            case ':':
                showHelp();
                exit(0);       
            break;
            
        }
        opt = getopt_long(argc, argv, optString, longOpts, &longIndex);
    }
    
    if(!RomFilename){
        showHelp();
        fprintf(stderr, "ROM Filename not supplied! Please do so passing the -f argument!\n\n");
        exit(1);
    }
    
    if(initSDL() < 0){
        fprintf(stderr, "Error! Could not initialize SDL!");
        exit(1);
    }
    
    if(!Settings.xScaling)
        Settings.xScaling = 1;
    
    if(!Settings.yScaling)
        Settings.yScaling = 1;
    
    if((Settings.ArgumentFlags & (AF_PLAYING | AF_RECORDING)) == (AF_PLAYING | AF_RECORDING)){
        printf("Recording and playing a movie at the same time is not allowed!\n");
        exit(1);
    }
    
    retVal = system("rm dynlib/*.c dynlib/*.o dynlib/*.so");
    
    if((Settings.ArgumentFlags & (AF_DBT_PARALLEL_COMPILATION | AF_DBT)) == (AF_DBT_PARALLEL_COMPILATION | AF_DBT)){
        printf("Running jrynes using parallel compilation...\n");
        //CQ_Init();
        //PC_Init(1);
        //SNN_ParallelScanningInit();
    }
    
    /*if((!thresholdSet) && (Settings.ArgumentFlags & AF_DBT)){
        printf("Please set the block size compilation threshold using -t. See --help for more info.\n");
        exit(1);
    }*/
    
    if(Settings.ArgumentFlags & AF_DBT){
        
        if(!epochSizeSet)
            Settings.EpochLength = 1000;
        
        printf("DBT Execution Settings:\n\n");
        printf("\tOptimizations: ");
        if(Settings.ArgumentFlags & AF_OPT_BLOCK_COALESCING)
            printf("[Block Coalescing] ");
        if(Settings.ArgumentFlags & AF_OPT_IMMEDIATE)
            printf("[Immediate Context] ");
        printf("\n\n");
        if(Settings.ArgumentFlags & AF_CTXT_INFO){
            printf("Contextual Information Parameters:\n");
            printf("\tRegister Flags: 0x%02X\n", Settings.ContextMask);
            printf("\tOptimization Request Threshold: %ld\n", Settings.ContextThreshold);
            printf("\tMaximum Contexts per Block: %d\n", Settings.MaxContexts);
            printf("\n");
        }
        printf("Parallel compilation technique: [%s]\n\n", Settings.ArgumentFlags & AF_DBT_PARALLEL_COMPILATION ? "YES" : "NO");
        printf("Minimum block size for compilation: %d\n\n", Settings.CompilationThreshold);
        printf("Epoch Length, in TUs: %ld\n\n", Settings.EpochLength);
    }
    
    InitStatistics();
    
    //Begin emulation
    NESrun(RomFilename);
    
    exit(0);
}

